
#ifndef ROPUFU_WINDOW_FIXED_HPP_INCLUDED
#define ROPUFU_WINDOW_FIXED_HPP_INCLUDED

#include <array>
#include <cstdint>
#include <ostream>
#include <string>
#include <vector>

namespace ropufu
{
    template <std::size_t t_count_streams>
    struct window_fixed
    {
        static const std::size_t count_streams = t_count_streams;
        typedef window_fixed<count_streams> type;

    private:
        std::size_t m_size;
        std::size_t m_time = 0UL;

        // The complete log-LR: (l_{1} + \cdots + l_{t}).
        std::array<double, count_streams> m_complete_loglikelihood;
        // The index of the (oldest) observation to be overwritten.
        std::array<std::size_t, count_streams> m_oldest_index;
        std::array<double, count_streams> m_loglikelihood_accumulated;

        // The (truncated) history or observations' accumulated log-likelihood ratio.
        // When not truncated, for each 1 \le k \le K, at each time t, stores (l_1 + \cdots + l_t, l_2 + \cdots + l_t, \cdots, l_{t - 1} + l_t, l_t).
        // When truncated, oldest observations (on the left) are removed. When overwritten, the right element may no longer be the latest one, e.g.:
        //
        // t = 5:   || l1 + l2 + l3 + l4 + l5 ||   l2 + l3 + l4 + l5    ||       l3 + l4 + l5     ||      l4 + l5      ||       l5     ||
        // t = 6:   ||           l6           || l2 + l3 + l4 + l5 + l6 ||    l3 + l4 + l5 + l6   ||    l4 + l5 + l6   ||    l5 + l6   ||
        // t = 7:   ||         l6 + l7        ||           l7           || l3 + l4 + l5 + l6 + l7 || l4 + l5 + l6 + l7 || l5 + l6 + l7 ||
        //
        std::array<std::vector<double>, count_streams> m_loglikelihood_histroy;

    public:
        explicit window_fixed(std::size_t size)
            : m_size(size)
        {
            for (std::size_t k = 0UL; k < count_streams; k++) this->m_loglikelihood_histroy[k].reserve(size);
            this->reset();
        }

        void reset()
        {
            this->m_time = 0UL;
            this->m_complete_loglikelihood.fill(0.0);
            this->m_oldest_index.fill(0UL);
            this->m_loglikelihood_accumulated.fill(0.0);
            for (std::size_t k = 0; k < count_streams; k++)
            {
                this->m_loglikelihood_histroy[k].clear();
            }
        }

        std::size_t get_depth() const
        {
            return this->m_loglikelihood_histroy[0].size(); // Note: same for all 1 \le k \le count_streams.
        }

        void observe(const std::array<double, count_streams>& loglikelihood)
        {
            for (std::size_t k = 0; k < count_streams; k++) this->m_complete_loglikelihood[k] += loglikelihood[k];
            for (std::size_t k = 0; k < count_streams; k++)
            {
                double l = loglikelihood[k];
                std::vector<double>& local_histroy = this->m_loglikelihood_histroy[k]; // Just to make things more readable.
                for (double& u : local_histroy) u += l; // Update the accumulated log-LR history with current observation.
                this->m_loglikelihood_accumulated[k] += l;

                // Append the current observation to the history if the capacity hasn't been reached yet.
                if (local_histroy.size() < this->m_size) local_histroy.emplace_back(l);
                else
                {
                    // When capacity has been reached, overwrite the oldest observation.
                    //std::cout << "Window exceeded at time " << this->m_time + 1 << "." << std::endl;
                    local_histroy[this->m_oldest_index[k]] = l;
                    this->m_oldest_index[k] = (this->m_oldest_index[k] + 1) % this->m_size;
                }
            }
            this->m_time++;
        }

        // Returns the complete loglikelihood ratio.
        const std::array<double, count_streams>& complete() const
        {
            return this->m_complete_loglikelihood;
        }

        // Returns the accumulated loglikelihood ratio.
        const std::array<double, count_streams>& accumulated() const
        {
            return this->m_loglikelihood_accumulated;
        }

        // Returns the history of observations since the last reset.
        const std::array<std::vector<double>, count_streams>& get_history() const
        {
            return this->m_loglikelihood_histroy;
        }

        double get_history_at(std::size_t stream_index, std::size_t time_index) const
        {
            return this->m_loglikelihood_histroy[stream_index][time_index];
        }

        friend std::ostream& operator <<(std::ostream& os, const type& that)
        {
            return os << "window_fixed_" << that.m_size;
        }
    };
}

#endif // ROPUFU_WINDOW_FIXED_HPP_INCLUDED
